#coding=utf-8

module Aio::Device
  class ParentDevice

    include Aio::Ui::Verbose

    attr_accessor :device_name

    # cmds_context = { "cmd_full_name" => [ {
    #					device_type_1 => klass1, 
    #					device_type_2 => klass2},
    #			  context ] }
    attr_accessor :cmds_context

    # 管理IP
    attr_accessor :manager_ip

    # 设备的基本信息
    attr_accessor :device_info

    # 按cmd名称管理有用信息
    # Hash = { cmd_name => { :case => useful } }
    attr_accessor :cmds_useful

    # Warning信息模块
    attr_accessor 	:warning_klass

    # 设备类型
    #attr_reader :device_type

    InvalidInput = /Invalid input detected/

    Interface = {
      geth:     "GigabitEthernet",
      feth:     "FastEthernet",
      eth:      "Ethernet",
      loopback: "Loopback",
      vlan_if:  "Vlan-interface"
    }

    # FIXME 无法获得正确的父类Interface
    def interface
      self.class::Interface
    end

    def initialize
      self.cmds_context = {}
      self.device_name = ""
      self.cmds_useful = {}

      @warning_klass = Aio::Text::Warning.new
      @device_type = ""
      @device_info = {}
    end

    def instance
      self
    end

    def inspect
      "#<#{self.class}>"
    end

    def device_type
      if @device_type.empty?
        return "other"
      end

      @device_type
    end

    # 判断是否有cmd的信息
    def has_cmd?(cmd)
      @cmds_useful.has_key?(cmd)
    end

    # 增加cmd
    # @Param klass_info 为Array
    # 			 klass_info = ["maybe_device_type", cmd_klass]
    def add_cmd(klass_info)
      maybe_device_type = klass_info[0]
      cmd_klass = klass_info[1]

      cmd_full_name = cmd_klass.cmd_full

      # 有可能已经有了其他设备类型的模块
      # 那么通过Hash的方式存储所有的模块
      cmds_context[cmd_full_name] ||= []
      cmds_context[cmd_full_name][0] ||= {}
      cmds_context[cmd_full_name][0][maybe_device_type] = cmd_klass
      return cmd_full_name
    end

    # 两种用法：
    # 在有klass的时候可以添加为完整cmds_context
    # 当没有klass的话，也可以加。那么klass项为nil
    def add_cmd_context_by_cmd_name(cmd_full_name, context)
      cmds_context[cmd_full_name] ||= []
      cmds_context[cmd_full_name][1] = context
    end

    # 整合上面两个方法，成为一步
    def add_cmd_context_by_klass(klass_info, context)
      cmd_full_name = add_cmd(klass_info)
      add_cmd_context_by_cmd_name(cmd_full_name, context)
      return cmds_context[cmd_full_name]
    end

    # 最终解析的地方
    # 一定要确定设备类型，否则的话只能单一设备
    # cmds_context = { cmd_name => [ {
    #					device_type_1 => klass1, 
    #					device_type_2 => klass2},
    #			  context ] }
    def parse

      # 做两次循环，第一次确定设备类型
      2.times do |t|
        cmds_context.each_pair do |cmd_name, info|
          context = info[1]

          # 如果没有cmd模块， 那么就跳过
          next if info[0].nil?

          info[0].each_pair do |device_type, module_klass|

            #module_klass.clear_useful

            # 如果找到了设备类型
            # 那么之后就只对该类型进行解析,其他的跳过
            if ! @device_type.empty? and @device_type != device_type
              next
            end

            module_klass.context = context
            module_klass.warning_klass = @warning_klass

            # 循环模块对内容进行解析
            # 当是第一遍遍历的时候，只找可以决定是什么设备类型的命令
            # 一般都是通过show version 这类命令找到， 那么同时也能找到设备型号和软件版本
            if t == 0 and not module_klass.key_stand
              next

            elsif t == 0 and module_klass.key_stand
              module_klass.parse
              push_device_info(module_klass)

              module_klass.clear_useful
              # 当device_type 有内容时，确定为已知设备类型，那么进行下次循环遍历
              if @device_type.empty?
                next
              else
                @warning_klass.clear
                break
              end
            end

            begin
              # 当是第二遍的时候，则全部解析
              module_klass.clear_useful
              module_klass.device_info = device_info
              module_klass.ext_info = {:device_name => @device_name}
              module_klass.parse
            rescue Aio::Text::Context::OutLineError
            rescue NoMethodError
              print_error "未找到方法，请检查模块的方法名拼写"
            rescue NameError
              print_error "#{module_klass.class} 未检测到对应版本"
            rescue Exception => e
              print_error "#{e.class} : #{module_klass.class}"
              print_error e.message
            ensure
              push_useful(module_klass, cmd_name)
              next
            end
          end
        end
      end

      # 当所有解析循环完成后，如果找到了设备类型
      # 就将此类转为对应子类
      # changeto
      if ! @device_type.empty?
        to = self.send("changeto_#{@device_type}")
      end

      return to ? to : self
    end

    # 存放设备基本信息
    def push_device_info(module_klass)
      if not module_klass.useful[:device_type].to_s.empty?
        @device_type = module_klass.useful[:device_type].to_s
      end

      if not module_klass.useful[:device_model].to_s.empty?
        @device_info[:device_model] = module_klass.useful[:device_model].to_s 
      end

      if not module_klass.useful[:device_version].to_s.empty?
        @device_info[:device_version] = module_klass.useful[:device_version].to_s
      end

      if not module_klass.useful[:device_template].to_s.empty?
        @device_info[:device_template] = module_klass.useful[:device_template].to_s
      end
    end

    # 当一个模块解析完毕后，将有用信息放入cmds_useful
    # 当有用信息中device_type类为真时，
    # 就可以将此父类转变为具体的类型了
    # check
    def push_useful(module_klass, cmd_name)

      if Aio::Base::Debug.enable?
        puts "parent_device#push_useful"
        pp module_klass
      end

      # 将有用信息放入整体有用信息
      cmds_useful[cmd_name] = module_klass.useful 
    end

=begin
    # 通过 method_missing， 注释掉的命令可以直接使用
    def warning_info
      warning_klass.warning_info
    end

    def warning_info_serious
      warning_klass.warning_info_serious
    end

    def warning_info_ordinary
      warning_klass.warning_info_ordinary
    end

    def warning_info_compare
      warning_klass.warning_info_compare
    end
=end

    # 1.导向命令方法模块
    # 2.导向Warning类
    def method_missing(m, *args, &block)
      if Aio::Device::Methods.respond_to?(m)
        Aio::Device::Methods.klass = self
        Aio::Device::Methods.send(m)

      elsif Aio::Text::Warning.instance_methods(false).include?(m.to_sym)
        warning_klass.send(m, *args)
      else
        super
      end
    end

    alias :name :device_name

    private

    def changeto_cisco
      to = Cisco.new
      clone(to)
    end

    def changeto_huawei
      to = Huawei.new
      clone(to)
    end

    def changeto_maipu
      to = Maipu.new
      clone(to)
    end

    def changeto_h3c
      to = H3C.new
      clone(to)
    end

    def clone(to)
      to.device_name 		= Aio::Base::Toolkit::DeepClone.clone(self.device_name)
      to.cmds_context 	= Aio::Base::Toolkit::DeepClone.clone(self.cmds_context)
      to.manager_ip			=	Aio::Base::Toolkit::DeepClone.clone(self.manager_ip)
      to.device_info 		= Aio::Base::Toolkit::DeepClone.clone(self.device_info)
      to.cmds_useful		= Aio::Base::Toolkit::DeepClone.clone(self.cmds_useful)
      to.warning_klass	= Aio::Base::Toolkit::DeepClone.clone(self.warning_klass)
      to
    end
  end
end

